]> git.saurik.com Git - apple/security.git/blob - OSX/Keychain Circle Notification/KNAppDelegate.m
Security-59754.80.3.tar.gz
[apple/security.git] / OSX / Keychain Circle Notification / KNAppDelegate.m
1 /*
2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
32 #import "notify.h"
33 #import <utilities/debugging.h>
34 #import <utilities/SecCFWrappers.h>
35 #import <utilities/SecXPCError.h>
36 #import <os/variant_private.h>
37
38 #import <Accounts/Accounts.h>
39 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
40 #import <AOSAccounts/MobileMePrefsCore.h>
41 #import <AOSAccounts/ACAccountStore+iCloudAccount.h>
42 #import <AOSAccounts/iCloudAccount.h>
43
44 #include <msgtracer_client.h>
45 #include <msgtracer_keys.h>
46 #include <CrashReporterSupport/CrashReporterSupportPrivate.h>
47 #import <ProtectedCloudStorage/CloudIdentity.h>
48 #import "CoreCDP/CDPFollowUpController.h"
49 #import "CoreCDP/CDPFollowUpContext.h"
50 #import <CoreCDP/CDPAccount.h>
51
52 static const char * const kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
53 static const NSString * const kKickedOutKey = @"KickedOut";
54 static const NSString * const kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
55 static const NSString * const kPasswordChangedOrTrustedDeviceChanged = @"TDorPasswordChanged";
56 static NSString *prefpane = @"/System/Library/PreferencePanes/iCloudPref.prefPane";
57 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
58 #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
59 static NSString *KeychainPCDetailsAEAction = @"AKPCDetailsAEAction";
60 bool _hasPostedAndStillInError = false;
61 bool _haveCheckedForICDPStatusOnceInCircle = false;
62 bool _isAccountICDP = false;
63
64 @implementation KNAppDelegate
65
66 static NSUserNotificationCenter *appropriateNotificationCenter()
67 {
68 return [NSUserNotificationCenter _centerForIdentifier: @"com.apple.security.keychain-circle-notification"
69 type: _NSUserNotificationCenterTypeSystem];
70 }
71
72 static BOOL isErrorFromXPC(CFErrorRef error)
73 {
74 // Error due to XPC failure does not provide information about the circle.
75 if (error && (CFEqual(sSecXPCErrorDomain, CFErrorGetDomain(error)))) {
76 return YES;
77 }
78 return NO;
79 }
80
81 static void PSKeychainSyncIsUsingICDP(void)
82 {
83 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
84 ACAccount *primaryiCloudAccount = nil;
85
86 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
87 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
88 }
89
90 NSString *dsid = primaryiCloudAccount.icaPersonID;
91 BOOL isICDPEnabled = NO;
92 if (dsid) {
93 isICDPEnabled = [CDPAccount isICDPEnabledForDSID:dsid];
94 NSLog(@"iCDP: PSKeychainSyncIsUsingICDP returning %@", isICDPEnabled ? @"TRUE" : @"FALSE");
95 } else {
96 NSLog(@"iCDP: no primary account");
97 }
98
99 _isAccountICDP = isICDPEnabled;
100 }
101
102 -(void) startFollowupKitRepair
103 {
104 NSError *localError = NULL;
105 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
106 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
107
108 secnotice("followup", "Posting a follow up (for SOS) of type repair");
109 [cdpd postFollowUpWithContext:context error:&localError ];
110 if(localError){
111 secnotice("kcn", "request to CoreCDP to follow up failed: %@", localError);
112 }
113 else{
114 secnotice("kcn", "CoreCDP handling follow up");
115 _hasPostedAndStillInError = false;
116 }
117 }
118
119 - (void) handleDismissedNotification
120 {
121 if(_isAccountICDP){
122 secnotice("kcn", "handling dismissed notification, would start a follow up");
123 [self startFollowupKitRepair];
124 }
125 else
126 secerror("unable to find primary account");
127 }
128
129 - (void) notifyiCloudPreferencesAbout: (NSString *) eventName
130 {
131 if (eventName == nil)
132 return;
133
134 secnotice("kcn", "notifyiCloudPreferencesAbout %@", eventName);
135
136 NSString *accountID = (__bridge_transfer NSString*)(MMCopyLoggedInAccountFromAccounts());
137 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
138 ACAccount *primaryiCloudAccount = nil;
139
140 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
141 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
142 }
143
144 if(primaryiCloudAccount){
145 AEDesc aeDesc;
146 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *) kMMServiceIDKeychainSync, eventName, accountID, &aeDesc);
147 if (createdAEDesc) {
148 NSArray *prefPaneURL = [NSArray arrayWithObject: [NSURL fileURLWithPath: prefpane ]];
149
150 LSLaunchURLSpec lsSpec = {
151 .appURL = NULL,
152 .itemURLs = (__bridge CFArrayRef)prefPaneURL,
153 .passThruParams = &aeDesc,
154 .launchFlags = kLSLaunchDefaults | kLSLaunchAsync,
155 .asyncRefCon = NULL,
156 };
157
158 OSErr err = LSOpenFromURLSpec(&lsSpec, NULL);
159
160 if (err)
161 secerror("Can't send event %@, err=%d", eventName, err);
162 AEDisposeDesc(&aeDesc);
163 } else {
164 secerror("unable to create and send aedesc for account: '%@' and action: '%@'\n", primaryiCloudAccount, eventName);
165 }
166 }
167 secerror("unable to find primary account");
168 }
169
170 - (void) timerCheck
171 {
172 NSDate *nowish = [NSDate new];
173
174 self.state = [KNPersistentState loadFromStorage];
175 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
176 secnotice("kcn", "REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
177
178 // self.circle.rawStatus might not be valid yet
179 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
180 // Still have a request pending, send reminder, and also in addtion to the UI
181 // we need to send a notification for iCloud pref pane to pick up
182 CFNotificationCenterPostNotificationWithOptions(
183 CFNotificationCenterGetDistributedCenter(),
184 CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"),
185 (__bridge const void *) [self.state.applicationDate description], NULL, 0
186 );
187
188 [self postApplicationReminder];
189 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
190 [self.state writeToStorage];
191 }
192 }
193 }
194
195
196 - (void) scheduleActivityAt: (NSDate *) time
197 {
198 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
199 NSTimeInterval howSoon = [time timeIntervalSinceNow];
200 if (howSoon > 0)
201 [self scheduleActivityIn:ceil(howSoon)];
202 else
203 [self timerCheck];
204 }
205 }
206
207
208 - (void) scheduleActivityIn: (int) alertInterval
209 {
210 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
211 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
212 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
213 xpc_dictionary_set_bool (options, XPC_ACTIVITY_REPEATING, false);
214 xpc_dictionary_set_bool (options, XPC_ACTIVITY_ALLOW_BATTERY, true);
215 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
216
217 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
218 [self timerCheck];
219 });
220 }
221
222
223 - (NSTimeInterval) getPendingApplicationReminderInterval
224 {
225 if (self.state.pendingApplicationReminderInterval)
226 return [self.state.pendingApplicationReminderInterval doubleValue];
227 else
228 return 24*60*60;
229 }
230
231 - (NSMutableSet *) makeApplicantSet {
232 KNAppDelegate *me = self;
233 NSMutableSet *applicantIds = [NSMutableSet new];
234 for (KDCirclePeer *applicant in me.circle.applicants) {
235 [me postForApplicant:applicant];
236 [applicantIds addObject:applicant.idString];
237 }
238 return applicantIds;
239 }
240
241 - (bool) removeAllNotificationsOfType: (NSString *) typeString {
242 bool didRemove = false;
243 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
244 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
245 if (note.userInfo[typeString]) {
246 [appropriateNotificationCenter() removeDeliveredNotification: note];
247 didRemove = true;
248 }
249 }
250 return didRemove;
251 }
252
253 static const char *sosCCStatusCString(SOSCCStatus status) {
254 switch(status) {
255 case kSOSCCError: return "kSOSCCError";
256 case kSOSCCInCircle: return "kSOSCCInCircle";
257 case kSOSCCNotInCircle: return "kSOSCCNotInCircle";
258 case kSOSCCCircleAbsent: return "kSOSCCCircleAbsent";
259 case kSOSCCRequestPending: return "kSOSCCRequestPending";
260 default: return "unknown";
261 }
262 }
263
264 static const char *sosDepartureReasonCString(enum DepartureReason departureReason){
265 switch(departureReason) {
266 case kSOSDepartureReasonError: return "kSOSDepartureReasonError";
267 case kSOSNeverLeftCircle: return "kSOSNeverLeftCircle";
268 case kSOSWithdrewMembership: return "kSOSWithdrewMembership";
269 case kSOSMembershipRevoked: return "kSOSMembershipRevoked";
270 case kSOSLeftUntrustedCircle: return "kSOSLeftUntrustedCircle";
271 case kSOSNeverAppliedToCircle: return "kSOSNeverAppliedToCircle";
272 case kSOSDiscoveredRetirement: return "kSOSDiscoveredRetirement";
273 case kSOSLostPrivateKey: return "kSOSLostPrivateKey";
274 default: return "unknown reason";
275 }
276 }
277
278
279 - (void) processCircleState {
280 CFErrorRef err = NULL;
281 KNAppDelegate *me = self;
282
283 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
284 if (isErrorFromXPC(err)) {
285 secnotice("kcn", "SOSCCGetLastDepartureReason failed with xpc error: %@", err);
286 CFReleaseNull(err);
287 return;
288 } else if (err) {
289 secnotice("kcn", "SOSCCGetLastDepartureReason failed with: %@", err);
290 }
291 CFReleaseNull(err);
292
293 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&err);
294 if (isErrorFromXPC(err)) {
295 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with xpc error: %@", err);
296 CFReleaseNull(err);
297 return;
298 } else if (err) {
299 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
300 }
301 CFReleaseNull(err);
302
303 NSDate *nowish = [NSDate date];
304 me.state = [KNPersistentState loadFromStorage];
305 SOSCCStatus lastCircleStatus = me.state.lastCircleStatus;
306
307 PSKeychainSyncIsUsingICDP();
308
309 secnotice("kcn", "processCircleState starting ICDP: %s SOSCCStatus: %s DepartureReason: %s",
310 (_isAccountICDP) ? "Enabled": "Disabled",
311 sosCCStatusCString(circleStatus),
312 sosDepartureReasonCString(departureReason));
313
314 if(_isAccountICDP){
315 me.state.lastCircleStatus = circleStatus;
316 [me.state writeToStorage];
317
318 switch(circleStatus) {
319 case kSOSCCInCircle:
320 secnotice("kcn", "iCDP: device is in circle!");
321 _hasPostedAndStillInError = false;
322 break;
323 case kSOSCCRequestPending:
324 [me scheduleActivityAt:me.state.pendingApplicationReminder];
325 break;
326 case kSOSCCCircleAbsent:
327 case kSOSCCNotInCircle:
328 [me outOfCircleAlert: departureReason];
329 secnotice("kcn", "{ChangeCallback} Pending request START");
330 me.state.applicationDate = nowish;
331 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
332 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
333 [me scheduleActivityAt:me.state.pendingApplicationReminder];
334 break;
335 case kSOSCCError:
336 /*
337 You would think we could count on not being iCDP if the account was signed out. Evidently that's wrong.
338 So we'll go based on the artifact that when the account object is reset (like by signing out) the
339 departureReason will be set to kSOSDepartureReasonError. So we won't push to get back into a circle if that's
340 the current reason. I've checked code for other ways we could be out. If we boot and can't load the account
341 we'll end up with kSOSDepartureReasonError. Then too if we end up in kSOSDepartureReasonError and reboot we end up
342 in the same place. Leave it to cdpd to decide whether the user needs to sign in to an account.
343 */
344 if(departureReason != kSOSDepartureReasonError) {
345 secnotice("kcn", "ICDP: We need the password to initiate trust");
346 [me postRequirePassword];
347 _hasPostedAndStillInError = true;
348 } else {
349 secnotice("kcn", "iCDP: We appear to not be associated with an iCloud account");
350 }
351 break;
352 default:
353 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
354 break;
355 }
356 } else { // SA version
357 switch(circleStatus) {
358 case kSOSCCInCircle:
359 secnotice("kcn", "SA: device is in circle!");
360 _hasPostedAndStillInError = false;
361 break;
362 case kSOSCCRequestPending:
363 [me scheduleActivityAt:me.state.pendingApplicationReminder];
364 secnotice("kcn", "{ChangeCallback} scheduleActivity %@", me.state.pendingApplicationReminder);
365 break;
366 case kSOSCCCircleAbsent:
367 case kSOSCCNotInCircle:
368 switch (departureReason) {
369 case kSOSDiscoveredRetirement:
370 case kSOSLostPrivateKey:
371 case kSOSWithdrewMembership:
372 case kSOSNeverAppliedToCircle:
373 case kSOSDepartureReasonError:
374 case kSOSMembershipRevoked:
375 default:
376 if(me.state.lastCircleStatus == kSOSCCInCircle) {
377 secnotice("kcn", "SA: circle status went from in circle to %s: reason: %s", sosCCStatusCString(circleStatus), sosDepartureReasonCString(departureReason));
378 }
379 break;
380
381 case kSOSNeverLeftCircle:
382 case kSOSLeftUntrustedCircle:
383 [me outOfCircleAlert: departureReason];
384 secnotice("kcn", "{ChangeCallback} Pending request START");
385 me.state.applicationDate = nowish;
386 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
387 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
388 [me scheduleActivityAt:me.state.pendingApplicationReminder];
389 break;
390 }
391 break;
392 case kSOSCCError:
393 if(me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
394 secnotice("kcn", "SA: circle status went from in circle to error - we need the password");
395 [me postRequirePassword];
396 _hasPostedAndStillInError = true;
397 }
398 break;
399 default:
400 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
401 break;
402 }
403 }
404
405
406 // Circle applications: pending request(s) started / completed
407 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
408 secnotice("kcn", "Pending request completed");
409 me.state.applicationDate = [NSDate distantPast];
410 me.state.pendingApplicationReminder = [NSDate distantFuture];
411 [me.state writeToStorage];
412
413 // Remove reminders
414 if([me removeAllNotificationsOfType: @"ApplicationReminder"]) {
415 secnotice("kcn", "{ChangeCallback} removed application remoinders");
416 }
417 }
418
419 // Clear out (old) reset notifications
420 if (circleStatus == kSOSCCInCircle) {
421 secnotice("kcn", "{ChangeCallback} kSOSCCInCircle");
422 if([me removeAllNotificationsOfType: (NSString*) kValidOnlyOutOfCircleKey]) {
423 secnotice("kcn", "Removed existing notifications now that we're in circle");
424 }
425 if([me removeAllNotificationsOfType: (NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
426 secnotice("kcn", "Removed existing password notifications now that we're in circle");
427 }
428
429 // Applicants
430 secnotice("kcn", "{ChangeCallback} Applicants");
431 NSMutableSet *applicantIds = [me makeApplicantSet];
432 // Clear applicant notifications that aren't pending any more
433 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
434 secnotice("kcn", "Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
435 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
436 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
437 secnotice("kcn", "No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
438 [notificationCenter removeDeliveredNotification:note];
439 } else {
440 secnotice("kcn", "Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
441 }
442 }
443 } else { // Clear any pending applicant notifications since we aren't in circle or invalid
444 if([me removeAllNotificationsOfType: (NSString*) @"applicantId"]) {
445 secnotice("kcn", "Not in circle or invalid - removed applicant notes");
446 }
447 }
448
449 me.state.lastCircleStatus = circleStatus;
450 [me.state writeToStorage];
451 }
452
453 - (void) applicationDidFinishLaunching: (NSNotification *) aNotification
454 {
455 appropriateNotificationCenter().delegate = self;
456 int out_taken;
457 int available;
458 secnotice("kcn", "Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
459
460 notify_register_dispatch(kPublicKeyAvailable, &available, dispatch_get_main_queue(), ^(int token) {
461 CFErrorRef err = NULL;
462 KNAppDelegate *me = self;
463 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&err);
464
465 if (isErrorFromXPC(err)) {
466 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
467 CFReleaseNull(err);
468 return;
469 }
470 CFReleaseNull(err);
471
472 me.state = [KNPersistentState loadFromStorage];
473
474 secnotice("kcn", "got public key available notification");
475 me.state.lastCircleStatus = currentCircleStatus;
476 [me.state writeToStorage];
477 });
478
479 //register for public key not available notification, if occurs KCN can react
480 notify_register_dispatch(kPublicKeyNotAvailable, &out_taken, dispatch_get_main_queue(), ^(int token) {
481 secnotice("kcn", "got public key not available notification");
482 KNAppDelegate *me = self;
483 [me processCircleState];
484 });
485
486 self.viewedIds = [NSMutableSet new];
487 self.circle = [KDSecCircle new];
488 KNAppDelegate *me = self;
489
490 [self.circle addChangeCallback:^{
491 secnotice("kcn", "{ChangeCallback}");
492 [me processCircleState];
493 }];
494 }
495
496
497 - (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
498 shouldPresentNotification: (NSUserNotification *) notification
499 {
500 return YES;
501 }
502
503
504 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
505 didActivateNotification: (NSUserNotification *) notification
506 {
507 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
508 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
509 }
510 }
511
512
513 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
514 didDismissAlert: (NSUserNotification *) notification
515 {
516 [self handleDismissedNotification];
517
518 // If we don't do anything here & another notification comes in we
519 // will repost the alert, which will be dumb.
520 id applicantId = notification.userInfo[@"applicantId"];
521 if (applicantId != nil) {
522 [self.viewedIds addObject:applicantId];
523 }
524 }
525
526
527 - (void) postForApplicant: (KDCirclePeer *) applicant
528 {
529 static int postCount = 0;
530
531 if ([self.viewedIds containsObject:applicant.idString]) {
532 secnotice("kcn", "Already viewed %@, skipping", applicant);
533 return;
534 }
535
536 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
537 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
538 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
539 if (note.isPresented) {
540 secnotice("kcn", "Already posted&presented: %@ (I=%@)", note, note.userInfo);
541 return;
542 } else {
543 secnotice("kcn", "Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
544 }
545 }
546 }
547
548 NSUserNotification *note = [NSUserNotification new];
549 note.title = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE), applicant.name];
550 note.informativeText = [KNAppDelegate localisedApprovalBodyWithDeviceTypeFromPeerInfo:applicant.peerObject];
551 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
552 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
553 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
554 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DECLINE);
555 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVE);
556 note.identifier = [[NSUUID new] UUIDString];
557 note.userInfo = @{
558 @"applicantName": applicant.name,
559 @"applicantId" : applicant.idString,
560 @"Activate" : (__bridge NSString *) kMMPropertyKeychainAADetailsAEAction,
561 };
562
563 secnotice("kcn", "About to post #%d/%lu (%@): %@", postCount, noteCenter.deliveredNotifications.count, applicant.idString, note);
564 [appropriateNotificationCenter() deliverNotification:note];
565 [self.viewedIds addObject:applicant.idString];
566 postCount++;
567 }
568
569 + (NSString *)localisedApprovalBodyWithDeviceTypeFromPeerInfo:(id)peerInfo {
570 NSString *type = (__bridge NSString *)SOSPeerInfoGetPeerDeviceType((__bridge SOSPeerInfoRef)(peerInfo));
571 CFStringRef localisedType = NULL;
572 if ([type isEqualToString:@"iPad"]) {
573 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPAD);
574 } else if ([type isEqualToString:@"iPhone"]) {
575 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPHONE);
576 } else if ([type isEqualToString:@"iPod"]) {
577 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPOD);
578 } else if ([type isEqualToString:@"Mac"]) {
579 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_MAC);
580 } else {
581 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_GENERIC);
582 }
583 return (__bridge_transfer NSString *)localisedType;
584 }
585
586 - (void) postRequirePassword
587 {
588 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(NULL);
589 if(currentCircleStatus != kSOSCCError) {
590 secnotice("kcn", "postRequirePassword when not needed");
591 return;
592 }
593
594 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(NULL);
595
596 if(_isAccountICDP){
597 secnotice("kcn","would have posted needs password and then followed up");
598 [self startFollowupKitRepair];
599 } else if(departureReason == kSOSNeverLeftCircle) { // The only SA case for prompting
600 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
601 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
602 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
603 if (note.isPresented) {
604 secnotice("kcn", "Already posted & presented: %@", note);
605 [appropriateNotificationCenter() removeDeliveredNotification: note];
606 } else {
607 secnotice("kcn", "Already posted, but not presented: %@", note);
608 }
609 }
610 }
611
612 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
613 if (os_variant_has_internal_ui("iCloudKeychain")) {
614 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), "Device became untrusted or password changed"];
615 message = [message stringByAppendingString: reason_str];
616 }
617
618 NSUserNotification *note = [NSUserNotification new];
619 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
620 note.informativeText = message;
621 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
622 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
623 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
624 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
625 note.identifier = [[NSUUID new] UUIDString];
626
627 note.userInfo = @{
628 kPasswordChangedOrTrustedDeviceChanged : @1,
629 @"Activate" : (__bridge NSString *) kMMPropertyKeychainPCDetailsAEAction,
630 };
631
632 secnotice("kcn", "body=%@", note.informativeText);
633 secnotice("kcn", "About to post #-/%lu (PASSWORD/TRUSTED DEVICE): %@", noteCenter.deliveredNotifications.count, note);
634 [appropriateNotificationCenter() deliverNotification:note];
635 } else {
636 secnotice("kcn", "postRequirePassword when not needed for SA");
637 }
638 }
639
640 - (void) outOfCircleAlert: (int) reason
641 {
642
643 if(!_isAccountICDP){
644 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
645 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
646 if (note.userInfo[(NSString*) kKickedOutKey]) {
647 if (note.isPresented) {
648 secnotice("kcn", "Already posted&presented (removing): %@", note);
649 [appropriateNotificationCenter() removeDeliveredNotification: note];
650 } else {
651 secnotice("kcn", "Already posted, but not presented: %@", note);
652 }
653 }
654 }
655
656 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
657 if (os_variant_has_internal_ui("iCloudKeychain")) {
658 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), sosDepartureReasonCString(reason)];
659 message = [message stringByAppendingString: reason_str];
660 }
661
662 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
663 // Contrary to HI spec (and I think it makes more sense)
664 // 1. otherButton == top : Not Now
665 // 2. actionButton == bottom: Continue
666 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
667 NSUserNotification *note = [NSUserNotification new];
668 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
669 note.informativeText = message;
670 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
671 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
672 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
673 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
674 note.identifier = [[NSUUID new] UUIDString];
675
676 note.userInfo = @{
677 kKickedOutKey : @1,
678 kValidOnlyOutOfCircleKey: @1,
679 @"Activate" : (__bridge NSString *) kMMPropertyKeychainMRDetailsAEAction,
680 };
681
682 secnotice("kcn", "body=%@", note.informativeText);
683 secnotice("kcn", "About to post #-/%lu (KICKOUT): %@", noteCenter.deliveredNotifications.count, note);
684 [appropriateNotificationCenter() deliverNotification:note];
685 }
686
687 else{
688 secnotice("kcn","outOfCircleAlert starting followup repair");
689 [self startFollowupKitRepair];
690 }
691 }
692
693 - (void) postApplicationReminder
694 {
695 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
696 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
697 if (note.userInfo[@"ApplicationReminder"]) {
698 if (note.isPresented) {
699 secnotice("kcn", "Already posted&presented (removing): %@", note);
700 [appropriateNotificationCenter() removeDeliveredNotification: note];
701 } else {
702 secnotice("kcn", "Already posted, but not presented: %@", note);
703 }
704 }
705 }
706
707 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
708 // Contrary to HI spec (and I think it makes more sense)
709 // 1. otherButton == top : Not Now
710 // 2. actionButton == bottom: Continue
711 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
712 NSUserNotification *note = [NSUserNotification new];
713 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_OSX);
714 note.informativeText = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_BODY_OSX);
715 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
716 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
717 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
718 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
719 note.identifier = [[NSUUID new] UUIDString];
720
721 note.userInfo = @{
722 @"ApplicationReminder" : @1,
723 kValidOnlyOutOfCircleKey: @1,
724 @"Activate" : (__bridge NSString *) kMMPropertyKeychainWADetailsAEAction,
725 };
726
727 secnotice("kcn", "About to post #-/%lu (REMINDER): %@ (I=%@)", noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
728 [appropriateNotificationCenter() deliverNotification:note];
729 }
730
731 @end